/**
 * 策略模式
 */
//  策略模式用一个成语就可以概括 —— 殊途同归。当我们做同一件事有多种方法时，
// 就可以将每种方法封装起来，在不同的场景选择不同的策略，调用不同的方法

// 策略模式（Strategy Pattern）：定义了一系列算法，并将每一个算法封装起来，而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。

// 排序算法接口

interface ISort {
    sort(arr: number[]): void;
}

// 冒泡排序：
class BubbleSort implements ISort {
    sort(arr: number[]): void {
        for (let i = 0; i < arr.length - 1; i++) {
            for (let j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    // 如果左边的数大于右边的数，则交换，保证右边的数字最大
                    arr[j + 1] = arr[j + 1] + arr[j];
                    arr[j] = arr[j + 1] - arr[j];
                    arr[j + 1] = arr[j + 1] - arr[j];
                }
            }
        }
    }
}

// 选择排序
class SelectionSort implements ISort {
    sort(arr: number[]) {
        let minIndex: number;
        for (let i = 0; i < arr.length - 1; i++) {
            minIndex = i;
            for (let j = i + 1; j < arr.length; j++) {
                if (arr[minIndex] > arr[j]) {
                    // 记录最小值的下标
                    minIndex = j;
                }
            }
            // 将最小元素交换至首位
            const temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
    }
}

// 插入排序
class InsertSort implements ISort {
    sort(arr: number[]) {
        // 从第二个数开始，往前插入数字
        for (let i = 1; i < arr.length; i++) {
            const currentNumber = arr[i];
            let j = i - 1;
            // 寻找插入位置的过程中，不断地将比 currentNumber 大的数字向后挪
            while (j >= 0 && currentNumber < arr[j]) {
                arr[j + 1] = arr[j];
                j--;
            }
            // 两种情况会跳出循环：1. 遇到一个小于或等于 currentNumber 的数字，跳出循环，currentNumber 就坐到它后面。
            // 2. 已经走到数列头部，仍然没有遇到小于或等于 currentNumber 的数字，也会跳出循环，此时 j 等于 -1，currentNumber 就坐到数列头部。
            arr[j + 1] = currentNumber;
        }
    }
}

class Sort implements ISort {
    private _sort: ISort;
    constructor(sort: ISort) {
        this._sort = sort;
    }
    sort(arr: number[]) {
        this._sort.sort(arr);
    }
    // 客户端通过此方法设置不同的策略
    setSort(sort: ISort) {
        this._sort = sort;
    }
}
const arr1 = [6, 1, 2, 3, 5, 4];
const sort = new Sort(new BubbleSort());
// 这里可以选择不同的策略完成排序
// sort.setSort(new InsertSort());
// sort.setSort(new SelectionSort());
sort.sort(arr1);
// 输出 [1, 2, 3, 4, 5, 6]
console.log(arr1);

// 所以使用策略模式时，更好的做法是与工厂模式结合，将不同的策略对象封装到工厂类中，
// 用户只需要传递不同的策略类型，然后从工厂中拿到对应的策略对象即可。接下来我们就来一起实现这种工厂模式与策略模式结合的混合模式。
enum SortStrategy {
    BUBBLE_SORT,
    SELECTION_SORT,
    INSERT_SORT,
}
class Sort2 implements ISort {
    private _sort: ISort = new BubbleSort();

    constructor(strategy: SortStrategy) {
        this.setStrategy(strategy);
    }
    sort(arr: number[]) {
        this._sort.sort(arr);
    }
    // 客户端通过此方法设置不同的策略
    setStrategy(strategy: SortStrategy) {
        switch (strategy) {
            case SortStrategy.BUBBLE_SORT:
                this._sort = new BubbleSort();
                break;
            case SortStrategy.SELECTION_SORT:
                this._sort = new SelectionSort();
                break;
            case SortStrategy.INSERT_SORT:
                this._sort = new InsertSort();
                break;
            default:
                throw new Error("There's no such strategy yet.");
        }
    }
}

const arr2 = [6, 1, 2, 3, 5, 4];
const sort2 = new Sort2(SortStrategy.BUBBLE_SORT);
// 可以通过选择不同的策略完成排序
// sort.setStrategy(SortStrategy.SELECTION_SORT);
// sort.setStrategy(SortStrategy.INSERT_SORT);
sort2.sort(arr2);
// 输出 [1, 2, 3, 4, 5, 6]
console.log(arr2);

// 使用策略模式时，程序只需选择一种策略就可以完成某件事。也就是说每个策略类都是完整的，都能独立完成这件事情，如上文所言，强调的是殊途同归。
// 使用状态模式时，程序需要在不同的状态下不断切换才能完成某件事，每个状态类只能完成这件事的一部分，需要所有的状态类组合起来才能完整的完成这件事，强调的是随势而动。
